#region Copyright 
/*-------------------------------------------------------------------------
* 命名空间名称/文件名:    SSPivot.Common.Extensions/TypeExtensions 
* CLR版本:     4.0.30319.42000
* 机器名称:    DESKTOP-NHMLO8A
* 功 能：       N/A
* 类 名：       TypeExtensions
* 创建时间：  2025/5/22 9:51:07
* 版本号：      v1.0
* 创建人:        xulong
*-------------------------------------------------------------------------*/
#endregion
using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations.Schema;
using SSPivot.Common;
using System.Linq.Expressions;
using System.Diagnostics;

namespace System
{
    public static class TypeExtensions
    {
        public static string GetFullNameWithAssemblyName(this Type type)
        {
            return type.FullName + ", " + type.Assembly.GetName().Name;
        }
        public static bool IsAssignableTo<TTarget>([NotNull] this Type type)
        {
            Check.NotNullVoid(type, nameof(type));

            return type.IsAssignableTo(typeof(TTarget));
        }
        public static bool IsAssignableTo([NotNull] this Type type, [NotNull] Type targetType)
        {
            Check.NotNullVoid(type, nameof(type));
            Check.NotNullVoid(targetType, nameof(targetType));

            return targetType.IsAssignableFrom(type);
        }
        public static Type[] GetBaseClasses([NotNull] this Type type, bool includeObject = true)
        {
            Check.NotNullVoid(type, nameof(type));

            var types = new List<Type>();
            AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject);
            return types.ToArray();
        }
        public static Type[] GetBaseClasses([NotNull] this Type type, Type stoppingType, bool includeObject = true)
        {
            Check.NotNullVoid(type, nameof(type));

            var types = new List<Type>();
            AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject, stoppingType);
            return types.ToArray();
        }
        private static void AddTypeAndBaseTypesRecursively(
            [NotNull] List<Type> types,
            [CanBeNull] Type type,
            bool includeObject,
            [CanBeNull] Type stoppingType = null)
        {
            if (type == null || type == stoppingType)
            {
                return;
            }

            if (!includeObject && type == typeof(object))
            {
                return;
            }

            AddTypeAndBaseTypesRecursively(types, type.BaseType, includeObject, stoppingType);
            types.Add(type);
        }
        public static T CreateInstance<T>([NotNull] this Type @this) => (T)Activator.CreateInstance(@this);
        public static T CreateInstance<T>([NotNull] this Type @this, params object[] args) => (T)Activator.CreateInstance(@this, args);

        public static bool IsNullableType([NotNull] this Type @this)
            => @this.IsGenericType && @this.GetGenericTypeDefinition() == typeof(Nullable<>);
        public static object GetDefaultValue([NotNull] this Type @this)
                   => @this.IsValueType && @this != typeof(void) ? ReflectionDictionary.TypeObejctCache.GetOrAdd(@this, Activator.CreateInstance) : null;
        public static Type Unwrap([NotNull] this Type type)
                   => Nullable.GetUnderlyingType(type) ?? type;
        public static Type GetUnderlyingType([NotNull] this Type type)
                   => Nullable.GetUnderlyingType(type);
        public static MethodInfo GetMethodBySignature([NotNull] this Type @this, MethodInfo method)
        {
            var methods = @this.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(x => x.Name.Equals(method.Name))
                .ToArray();

            var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();
            if (method.ContainsGenericParameters)
            {
                foreach (var info in methods)
                {
                    var innerParams = info.GetParameters();
                    if (innerParams.Length != parameterTypes.Length)
                    {
                        continue;
                    }

                    var idx = 0;
                    foreach (var param in innerParams)
                    {
                        if (!param.ParameterType.IsGenericParameter
                            && !parameterTypes[idx].IsGenericParameter
                            && param.ParameterType != parameterTypes[idx]
                        )
                        {
                            break;
                        }

                        idx++;
                    }
                    if (idx < parameterTypes.Length)
                    {
                        continue;
                    }

                    return info;
                }

                return null;
            }

            var baseMethod = @this.GetMethod(method.Name, parameterTypes);
            return baseMethod;
        }

        public static MethodInfo GetBaseMethod([NotNull] this MethodInfo @this)
            => @this.DeclaringType.BaseType.GetMethodBySignature(@this);

        public static bool IsVisibleAndVirtual([NotNull] this PropertyInfo @this)
            => (@this.CanRead && @this.GetMethod.IsVisibleAndVirtual()) || (@this.CanWrite && @this.GetMethod.IsVisibleAndVirtual());

        public static bool IsVisibleAndVirtual([NotNull] this MethodInfo @this)
        {
            if (@this.IsStatic || @this.IsFinal)
                return false;

            return @this.IsVirtual &&
                   (@this.IsPublic || @this.IsFamily || @this.IsFamilyOrAssembly);
        }

        public static bool IsVisible([NotNull] this MethodBase @this)
            => @this.IsPublic || @this.IsFamily || @this.IsFamilyOrAssembly;
        public static string GetDisplayName([NotNull] this MemberInfo @this)
                   => @this.GetCustomAttribute<DisplayNameAttribute>()?.DisplayName ?? @this.GetCustomAttribute<DisplayAttribute>()?.Name ?? @this.Name;
        public static string GetColumnName([NotNull] this PropertyInfo propertyInfo)
                   => propertyInfo.GetCustomAttribute<ColumnAttribute>()?.Name ?? propertyInfo.Name;
        public static string GetDescription([NotNull] this MemberInfo @this)
                    => @this.GetCustomAttribute<DescriptionAttribute>()?.Description ?? String.Empty;
        public static FieldInfo GetField<T>([NotNull] this T @this, string name, BindingFlags bindingAttr)
                   => @this.GetType().GetField(name, bindingAttr);
        public static FieldInfo GetField<T>([NotNull] this T @this, string name)
                   => ReflectionDictionary.TypeFieldCache.GetOrAdd(@this.GetType(), t => t.GetFields()).FirstOrDefault(_ => _.Name == name);
        public static FieldInfo[] GetFields([NotNull] this object @this, BindingFlags bindingAttr)
                   => @this.GetType().GetFields(bindingAttr);
        public static FieldInfo[] GetFields([NotNull] this object @this)
                   => ReflectionDictionary.TypeFieldCache.GetOrAdd(@this.GetType(), t => t.GetFields());
        public static object GetFieldValue<T>([NotNull] this T @this, string fieldName)
        {
            var field = @this.GetField(fieldName);
            return field?.GetValue(@this);
        }
        public static MethodInfo GetMethod<T>([NotNull] this T @this, string name, BindingFlags bindingAttr)
            => @this.GetType().GetMethod(name, bindingAttr);
        public static MethodInfo GetMethod<T>([NotNull] this T @this, string name)
                    => ReflectionDictionary.TypeMethodCache.GetOrAdd(@this.GetType(), t => t.GetMethods()).FirstOrDefault(_ => _.Name == name);
        public static MethodInfo[] GetMethods<T>([NotNull] this T @this, BindingFlags bindingAttr)
                   => @this.GetType().GetMethods(bindingAttr);
        public static MethodInfo[] GetMethods<T>([NotNull] this T @this)
                   => ReflectionDictionary.TypeMethodCache.GetOrAdd(@this.GetType(), t => t.GetMethods());
        public static PropertyInfo GetProperty<T>([NotNull] this T @this, string name, BindingFlags bindingAttr)
                   => @this.GetType().GetProperty(name, bindingAttr);
        public static PropertyInfo GetProperty<T>([NotNull] this T @this, [NotNull] string name)
                    => ReflectionDictionary.TypePropertyCache.GetOrAdd(@this.GetType(), type => type.GetProperties()).FirstOrDefault(_ => _.Name == name);
        public static MemberInfo GetPropertyOrField<T>([NotNull] this T @this, string name)
        {
            var property = @this.GetProperty(name);
            if (property != null)
            {
                return property;
            }

            var field = @this.GetField(name);
            return field;
        }
        public static PropertyInfo[] GetProperties([NotNull] this object @this)
            => ReflectionDictionary.TypePropertyCache.GetOrAdd(@this.GetType(), type => type.GetProperties());
        public static PropertyInfo[] GetProperties([NotNull] this object @this, BindingFlags bindingAttr)
                   => @this.GetType().GetProperties(bindingAttr);
        public static object GetPropertyValue<T>([NotNull] this T @this, string propertyName)
        {
            var property = @this.GetProperty(propertyName);

            return property?.GetValueGetter<T>()?.Invoke(@this);
        }
        public static object InvokeMethod<T>([NotNull] this T obj, string methodName, params object[] parameters)
        {
            var type = obj.GetType();
            var method = type.GetMethod(methodName, parameters.Select(o => o.GetType()).ToArray());

            return method?.Invoke(obj, parameters);
        }
        public static T InvokeMethod<T>([NotNull] this object obj, string methodName, params object[] parameters)
        {
            var type = obj.GetType();
            var method = type.GetMethod(methodName, parameters.Select(o => o.GetType()).ToArray());

            var value = method?.Invoke(obj, parameters);
            return value.ToOrDefault<T>();
        }
        public static bool IsAttributeDefined([NotNull] this object @this, Type attributeType, bool inherit = true)
            => @this.GetType().IsDefined(attributeType, inherit);
        public static bool IsAttributeDefined<T>([NotNull] this object @this, bool inherit = true) where T : Attribute
                    => @this.GetType().IsDefined(typeof(T), inherit);
        public static void SetFieldValue<T>([NotNull] this T @this, string fieldName, object value)
        {
            var type = @this.GetType();
            var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
            field?.SetValue(@this, value);
        }
        public static void SetPropertyValue<T>([NotNull] this T @this, string propertyName, object value) where T : class
        {
            var property = @this.GetProperty(propertyName);
            property?.GetValueSetter()?.Invoke(@this, value);
        }
        public static Func<T, object> GetValueGetter<T>([NotNull] this PropertyInfo @this)
        {
            return StrongTypedDictionary<T>.PropertyValueGetters.GetOrAdd(@this, prop =>
            {
                if (!prop.CanRead)
                    return null;

                var instance = Expression.Parameter(typeof(T), "i");
                var property = Expression.Property(instance, prop);
                var convert = Expression.TypeAs(property, typeof(object));
                return (Func<T, object>)Expression.Lambda(convert, instance).Compile();
            });
        }
        public static Func<object, object> GetValueGetter([NotNull] this PropertyInfo @this)
        {
            return ReflectionDictionary.PropertyValueGetters.GetOrAdd(@this, prop =>
            {
                if (!prop.CanRead)
                    return null;

                Debug.Assert(@this.DeclaringType != null);

                var instance = Expression.Parameter(typeof(object), "obj");
                var getterCall = Expression.Call(@this.DeclaringType.IsValueType
                    ? Expression.Unbox(instance, @this.DeclaringType)
                    : Expression.Convert(instance, @this.DeclaringType), prop.GetGetMethod());
                var castToObject = Expression.Convert(getterCall, typeof(object));
                return (Func<object, object>)Expression.Lambda(castToObject, instance).Compile();
            });
        }
        public static Action<object, object> GetValueSetter([NotNull] this PropertyInfo @this)
        {
            return ReflectionDictionary.PropertyValueSetters.GetOrAdd(@this, prop =>
            {
                if (!prop.CanWrite)
                    return null;

                var obj = Expression.Parameter(typeof(object), "o");
                var value = Expression.Parameter(typeof(object));

                Debug.Assert(@this.DeclaringType != null);

                // Note that we are using Expression.Unbox for value types and Expression.Convert for reference types
                var expr =
                    Expression.Lambda<Action<object, object>>(
                        Expression.Call(
                            @this.DeclaringType.IsValueType
                                ? Expression.Unbox(obj, @this.DeclaringType)
                                : Expression.Convert(obj, @this.DeclaringType),
                            @this.GetSetMethod(),
                            Expression.Convert(value, @this.PropertyType)),
                        obj, value);
                return expr.Compile();
            });
        }
        public static Action<T, object> GetValueSetter<T>([NotNull] this PropertyInfo @this) where T : class
        {
            return StrongTypedDictionary<T>.PropertyValueSetters.GetOrAdd(@this, prop =>
            {
                if (!prop.CanWrite)
                    return null;

                var instance = Expression.Parameter(typeof(T), "i");
                var argument = Expression.Parameter(typeof(object), "a");
                var setterCall = Expression.Call(instance, prop.GetSetMethod(), Expression.Convert(argument, prop.PropertyType));
                return (Action<T, object>)Expression.Lambda(setterCall, instance, argument).Compile();
            });
        }
        public static bool HasEmptyConstructor([NotNull] this Type @this)
            => @this.GetConstructors(BindingFlags.Instance).Any(c => c.GetParameters().Length == 0);
        public static bool IsValueTuple<T>([NotNull] this T t)
                    => typeof(T).IsValueTuple();
        public static bool IsValueType<T>([NotNull] this T t)
                  => typeof(T).IsValueType;
        public static bool IsArray<T>([NotNull] this T @this)
                   => @this.GetType().IsArray;
        public static bool IsClass<T>([NotNull] this T @this)
                  => @this.GetType().IsClass;
        public static bool IsEnum<T>([NotNull] this T @this)
                   => typeof(T).IsEnum;
        public static bool IsSubclassOf<T>([NotNull] this T @this, Type type)
                  => typeof(T).IsSubclassOf(type);
        public static T GetAttribute<T>([NotNull] this Assembly @this) where T : Attribute
        {
            var configAttributes = Attribute.GetCustomAttributes(@this, typeof(T), false);

            if (configAttributes != null && configAttributes.Length > 0)
            {
                return (T)configAttributes[0];
            }

            return null;
        }
        public static string ToDescription(this Type type)
        {
            var desc = type?.GetCustomAttribute<DescriptionAttribute>(false);
            return desc?.Description;
        }
    }
}
